package de.hsb.ai.learning;

import game.Transport;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.hsb.ai.learning.filter.FugitiveEntryFilter;
import de.hsb.ai.util.ScotlandJadeHelper;
import de.hsb.ai.util.ScotlandJadeGameLogger.Entry;
import de.hsb.ai.util.map.ScotlandJadeMap;
import de.hsb.ai.util.map.ScotlandJadeMapHelper;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;

/**
 * PreProcessor analyzes moves made by the fugitive.
 */
public class FugitiveAnalyzer implements Replay.Listener {

	public static final Logger logger = LoggerFactory.getLogger("sy.analyzer");

	private final ScotlandJadeMap map;

	private final List<ScotlandJadeVertex> positions;

	private Map<Integer, Integer> fugitivePositionHitCount = new HashMap<Integer, Integer>(20);
	
	private List<Transition> fugitiveTransitions = new LinkedList<Transition>();

	private int fugitiveLastTransport = Transport.NONE;
	
	private FugitiveEntryFilter filter;

	public FugitiveAnalyzer(ScotlandJadeMap map, List<ScotlandJadeVertex> positions) {
		this.map		= map;
		this.positions	= positions;
	}

	public FugitiveAnalyzer(ScotlandJadeMap map, List<ScotlandJadeVertex> positions, FugitiveEntryFilter filter) {
		this(map, positions);
		this.filter = filter;
	}

	public Collection<? extends Transition> getTransitions() {
		return fugitiveTransitions;
	}

	@Override
	public void beforeReplay(Replay replay) {

	}

	@Override
	public void handleEntry(Replay replay, Entry entry) {
		if (!FugitiveEntryFilter.isFugitive(entry.idx)) return;

		if (filter == null || filter.accepts(replay, entry)) {
			try {
				TransitionBuilder builder = new TransitionBuilder(positions.get(entry.idx), replay.getFugitiveMoveCount(), fugitiveLastTransport, entry);
				fugitiveTransitions.addAll(builder.getTransitions());
			} catch (Exception e) {
				logger.error("error at entry: " + entry.idx + " to " + entry.node, e);
			}
		}
		
		fugitiveLastTransport = entry.transport;
	}

	@Override
	public void afterReplay(Replay replay) {
	}

	public TransitionBuilder getTransitionBuilder(ScotlandJadeVertex source, int moveNumber, int lastTransport) {
		return new TransitionBuilder(source, moveNumber, lastTransport);
	}

	private State analyzeState(State state, int moveNumber, int transport, ScotlandJadeVertex vertex) {
		analyzeTransports(transport, vertex, state);
		analyzeDetectives(vertex, state);
		analzyeHitCount(vertex, state);
		analyzeReveal(moveNumber, state);

		return state;
	}

	private void analzyeHitCount(ScotlandJadeVertex vertex, State state) {
		Integer hitCount = fugitivePositionHitCount.get(vertex.getIndex());
		fugitivePositionHitCount.put(vertex.getIndex(), hitCount == null ? 1 : ++hitCount);

		state.setHits(hitCount == null ? 0 : hitCount);
	}

	private void analyzeReveal(int move, State state) {
		boolean hasToReveal = ScotlandJadeHelper.hasToRevealAt(move);
		state.setReveal(hasToReveal);
	}

	private void analyzeDetectives(ScotlandJadeVertex vertex, State state) {
		double[] costs = new double[positions.size() - 1];

		for (int i = 1; i < positions.size(); ++i) {
			try {
				costs[i-1] = ScotlandJadeMapHelper.info.shortestDistance(vertex, positions.get(i));
//				logger.info("--> detective-dist from " + vertex  + " to " + positions.get(i) + ": " + costs[i-1]);
			} catch (IllegalArgumentException e) {
				throw new IllegalArgumentException("Path from " + vertex + " to " + positions.get(i) + " failed", e);
			}
		}

		Arrays.sort(costs);
		state.setClosest(costs[0]);
	}

	private void analyzeTransports(int transport, ScotlandJadeVertex vertex, State state) {
		state.setTransport(transport);

		Set<ScotlandJadeEdge> targetEdges = map.edgesOf(vertex);
		int numT = 0, numB = 0, numU = 0, numF = 0;

		for (ScotlandJadeEdge edge : targetEdges) {
			switch (edge.getType()) {
			case Transport.FERRY: ++numF; break;
			case Transport.TAXI: ++numT; break;
			case Transport.BUS: ++numB; break;
			case Transport.UG: ++numU; break;
			}
		}

		state.setConnectivity(ScotlandJadeMapHelper.info.getConnectivity(targetEdges.size()));

		state.setHasFerry(numF > 0);
		state.setHasBus(numB > 0);
		state.setHasUnderground(numU > 0);
	}

	public class TransitionBuilder {

		private final int move;
		private final Entry entry;
		private final State sourceState;
		private final ScotlandJadeVertex source;

		public TransitionBuilder(ScotlandJadeVertex source, int moveNumber, int lastTransport, Entry entry) {
			this.move = moveNumber + 1;
			this.entry = entry;
			this.source = source;
			sourceState = analyzeState(new State(State.SOURCE_PREFIX, source.getIndex()), moveNumber, lastTransport, source);
		}

		public TransitionBuilder(ScotlandJadeVertex source, int moveNumber, int lastTransport) {
			this(source, moveNumber, lastTransport, null);
		}

		public Collection<Transition> getTransitions() {
			List<Transition> transitions = new LinkedList<Transition>();

			for (ScotlandJadeEdge e : map.edgesOf(source)) {
				Transition t = getTransition(e, true);
				if (t != null && (filter == null || filter.accepts(t))) {
					transitions.add(t);
				}
			}

			return transitions;
		}

		public Transition getTransition(ScotlandJadeEdge e, boolean emptyTargetsOnly) {
			ScotlandJadeVertex target = map.get(e.getTargetIndex(source.getIndex()));

			// Skip non empty targets
			if (emptyTargetsOnly && target.getType() != ScotlandJadeVertex.Type.EMPTY) {
				logger.warn("skipped " + target);
				return null;
			}

			// Analyze target state (target vertex)
			State targetState = analyzeState(new State(State.TARGET_PREFIX, target.getIndex()), move, e.getType(), target);

			if (entry != null) {
				boolean chosen = e.getType() == entry.transport && target.getIndex() == entry.node;
				return new Transition(sourceState, targetState, chosen);
			} else {
				return new Transition(sourceState, targetState);
			}
		}

	}

}
